package com.teach.filter;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.teach.commons.JsonUtil;
import com.teach.config.ZuulConfigProperies;
import com.teach.dto.AuthUser;
import com.teach.dto.ReturnData;
import com.teach.util.JwtUtil;

import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class JwtFilter  extends ZuulFilter {
	
	public static final String PREFIX = "Bearer ";
	
	@Autowired
	private ZuulConfigProperies config;
	
	@Autowired
	private JsonUtil jsonUtil;
	
	@Autowired
	private JwtUtil jwtUtil;
	
	/**
	 * -判断该过滤器是否需要被执行。返回true，则该过滤器对所有请求都会生效。
     * -实际运用中我们可以利用该函数来指定过滤器的有效范围。
	 */
	@Override
	public boolean shouldFilter() {
		if(config.skips!=null && config.skips.length>0) {
			RequestContext rc = RequestContext.getCurrentContext();
			String uri = rc.getRequest().getRequestURI();
			uri = uri.replaceAll("[\\/]+", "/");
			uri = uri.substring(uri.indexOf('/', 2));
			log.debug("uri:{}",uri);
			for(String str : config.skips) {
				if(uri.matches(str)) {
					return false;
				}
			}
			return true;
			
		}
		return false;
	}

	/**
	 * -过滤器的具体逻辑。这里我们通过rc.setSendZuulResponse(false)令 Zuul 过滤该请求，
	 * -不对其进行路由，然后通过rc.setResponseStatusCode(401)设置了其返回的错误码，
	 * -当然我们也可以进一步优化我们的返回，比如，通过ctx.setResponseBody(body)对返回 body 内容进行编辑等。
	 */
	@Override
	public Object run() throws ZuulException {
		RequestContext rc = RequestContext.getCurrentContext();
		try {
			String token = rc.getRequest().getHeader("Authorization");

			ReturnData<AuthUser> ret = check(token);
			
			//如果错误，则输出错误信息
			if(ret.getCode()!=200) {
				rc.setSendZuulResponse(false);
				rc.getResponse().setContentType("application/json;charset=UTF-8");
				rc.setResponseBody(jsonUtil.toString(ret));
				return null;
			}
			
			//网关转发请求头加入身份信息
			rc.addZuulRequestHeader("auth-user", Base64Utils.encodeToUrlSafeString(jsonUtil.toString(ret.getData()).getBytes()));
			
		}catch (Exception e) {
			log.warn(e.getMessage(),e);
			rc.setSendZuulResponse(false);
			rc.getResponse().setContentType("application/json;charset=UTF-8");
			rc.setResponseBody(jsonUtil.toString(new ReturnData<String>(HttpStatus.UNAUTHORIZED.value(), "认证错误", e.getMessage())));
			return null;
		}
		return null;
	}

	/**
	 * PRE：这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
	 * ROUTING：这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求，并使用 Apache HttpClient 或 Netfilx Ribbon 请求微服务。
	 * POST：这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
	 * ERROR：在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型，Zuul 还允许我们创建自定义的过滤器类型。例如，我们可以定制一种 STATIC 类型的过滤器，直接在 Zuul 中生成响应，而不将请求转发到后端的微服务
	 */
	@Override
	public String filterType() {
		return "PRE";
	}

	/**
	 * - filter执行顺序，通过数字指定。
	 * - 数字越大，优先级越低。
	 */
	@Override
	public int filterOrder() {
		return -100;
	}

	public ReturnData<AuthUser> check(String token){
		log.debug("认证Authorization：{}",token);
		if(StringUtils.isBlank(token)) {
			return new ReturnData<AuthUser>(HttpStatus.UNAUTHORIZED.value(), "请登录", null);
		}
		if(!token.startsWith(PREFIX)) {
			return new ReturnData<AuthUser>(HttpStatus.UNAUTHORIZED.value(), "请求头格式错误", null);
		}
		token = token.substring(PREFIX.length());
		try {
			AuthUser user = jwtUtil.getAuthUser(jwtUtil.parseJWT(token));
			return new ReturnData<AuthUser>(200, "ok", user);
		}catch (ExpiredJwtException e){
            log.error(e.getMessage(),e);
            if(e.getMessage().contains("Allowed clock skew")){
            	return new ReturnData<AuthUser>(HttpStatus.UNAUTHORIZED.value(), "认证过期", null);
            }else{
            	return new ReturnData<AuthUser>(HttpStatus.UNAUTHORIZED.value(), "认证失败", null);
            }
        }catch (Exception e) {
			log.warn(e.getMessage(),e);
			return new ReturnData<AuthUser>(HttpStatus.UNAUTHORIZED.value(), "认证失败", null);
		}
	}
}
